package cn.com.shopec.erp.common.orm.impl;

import java.io.Serializable;
import java.lang.reflect.Method;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.annotation.Resource;

import org.hibernate.Criteria;
import org.hibernate.HibernateException;
import org.hibernate.LockMode;
import org.hibernate.NonUniqueResultException;
import org.hibernate.Query;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.criterion.Criterion;
import org.hibernate.criterion.DetachedCriteria;
import org.hibernate.criterion.Order;
import org.hibernate.criterion.Projections;
import org.hibernate.criterion.Restrictions;
import org.springframework.orm.hibernate3.HibernateCallback;
import org.springframework.orm.hibernate3.HibernateTemplate;
import org.springframework.orm.hibernate3.support.HibernateDaoSupport;
import org.springframework.util.Assert;

import cn.com.shopec.erp.common.orm.IHibernateEntityDao;
import cn.com.shopec.erp.common.page.PageFinder;
import cn.com.shopec.erp.common.utils.reflection.ReflectionUtils;
import cn.com.shopec.erp.core.base.entity.BaseEntity;

/**
 * @Title: HibernateEntityDao.java
 * @Package: com.broadengate.common.orm.impl
 * @Description: 负责为单个Entity对象提供CRUD操作的Hibernate DAO基类.
 * @param <T>
 *            实体对象
 * @date: 2012-10-08
 * @version V1.0
 */
public class HibernateEntityDao<T> extends HibernateDaoSupport implements IHibernateEntityDao<T> {

    /**
     * 注入SessionFactory
     * 
     * @param sessionFactory
     */
    @Resource(name = "sessionFactory")
    public void setMySessionFactory(SessionFactory sessionFactory) {
        super.setSessionFactory(sessionFactory);
    }

    /**
     * DAO实体代理的Entity类型.
     */
    protected Class<T> entityClass;

    /**
     * 在构造函数中将泛型T.class赋给entityClass.
     */
    @SuppressWarnings("unchecked")
    public HibernateEntityDao() {
        this.entityClass = (Class<T>) ReflectionUtils.getSuperClassGenricType(this.getClass(), 0);
    }

    /**
     * 取得entityClass.JDK1.4不支持泛型的子类可以抛开Class<T> entityClass,重载此函数达到相同效果�?
     */
    protected Class<T> getEntityClass() {
        return this.entityClass;
    }

    @Override
    public Object saveObject(Object object) throws Exception {
        if (object instanceof BaseEntity) {
            Method method = object.getClass().getMethod(BaseEntity.ON_SAVE_METHOD_NAME);
            method.invoke(object);

            this.getHibernateTemplate().saveOrUpdate(object);
            return object;
        } else {
            this.getHibernateTemplate().saveOrUpdate(object);
            return object;
        }
    }

    @Override
    public T save(T object) throws Exception {
        if (object instanceof BaseEntity) {
            Method method = object.getClass().getMethod(BaseEntity.ON_SAVE_METHOD_NAME);
            method.invoke(object);

            this.getHibernateTemplate().saveOrUpdate(object);
            return object;
        } else {
            this.getHibernateTemplate().saveOrUpdate(object);
            return object;
        }
    }

    @Override
    public T update(T object) throws Exception {
        if (object instanceof BaseEntity) {
            Method method = object.getClass().getMethod(BaseEntity.ON_UPDATE_METHOD_NAME);
            method.invoke(object);

            return this.getHibernateTemplate().merge(object);
        } else {
            return this.getHibernateTemplate().merge(object);
        }
    }

    @Override
    public boolean remove(T object) throws Exception {
        this.getHibernateTemplate().delete(object);
        return true;
    }

    @Override
    public boolean removeById(Serializable id) throws Exception {
        return this.remove(this.getById(id));
    }

    @Override
    public List<T> getAll() throws Exception {
        HibernateTemplate template = this.getHibernateTemplate();
        template.setCacheQueries(true);
        return template.loadAll(this.entityClass);
    }

    @SuppressWarnings("unchecked")
    @Override
    public List<T> getAll(String orderBy, boolean isAsc) throws Exception {
        if (isAsc) {
            return this.getHibernateTemplate().findByCriteria(
                    DetachedCriteria.forClass(this.entityClass).addOrder(Order.asc(orderBy)));
        } else {
            return this.getHibernateTemplate().findByCriteria(
                    DetachedCriteria.forClass(this.entityClass).addOrder(Order.desc(orderBy)));
        }
    }

    @Override
    public T getById(Serializable id) throws Exception {
        return this.getById(id, false);
    }

    @SuppressWarnings("deprecation")
    @Override
    public T getById(Serializable id, boolean isLock) throws Exception {
        HibernateTemplate template = this.getHibernateTemplate();
        T obj = null;
        if (isLock) {
            obj = template.get(this.entityClass, id, LockMode.UPGRADE);
        } else {
            obj = template.get(this.entityClass, id);
        }
        return obj;
    }

    @Override
    public T findUniqueBy(final String propertyName, final Object value) {
        return this.getHibernateTemplate().execute(new HibernateCallback<T>() {

            @SuppressWarnings("unchecked")
            @Override
            public T doInHibernate(Session session) throws HibernateException, SQLException {

                return (T) DetachedCriteria.forClass(HibernateEntityDao.this.entityClass)
                        .add(Restrictions.eq(propertyName, value)).getExecutableCriteria(session).uniqueResult();
            }

        });
    }

    @SuppressWarnings("unchecked")
    @Override
    public PageFinder<T> pagedByHQL(final String hql, int toPage, int pageSize, Object... values) {
        String countQueryString = " select count(*) " + this.removeSelect(this.removeOrders(hql));
        List<T> countlist = this.getHibernateTemplate().find(countQueryString, values);
        Long totalCount = (Long) countlist.get(0);

        if (totalCount.intValue() < 1) {
            return new PageFinder<T>(toPage, totalCount.intValue(), pageSize);
        } else {
            final PageFinder<T> finder = new PageFinder<T>(toPage, totalCount.intValue(), pageSize);
            List<T> list = this.getHibernateTemplate().executeFind(new HibernateCallback<List<T>>() {
                @Override
                public List<T> doInHibernate(Session session) throws HibernateException, SQLException {
                    Query query = session.createQuery(hql);
                    query.setFirstResult(finder.getStartOfPage());
                    query.setMaxResults(finder.getPageSize());
                    return query.list();
                }
            });
            finder.setData(list);
            return finder;
        }
    }

    @Override
    public PageFinder<T> pagedByCriteria(DetachedCriteria detachedCriteria) {
        return this.pagedByCriteria(detachedCriteria, PageFinder.DEFAULT_PAGE_SIZE, 0);
    }

    @Override
    public PageFinder<T> pagedByCriteria(DetachedCriteria detachedCriteria, int startIndex) {
        return this.pagedByCriteria(detachedCriteria, PageFinder.DEFAULT_PAGE_SIZE, startIndex);
    }

    @SuppressWarnings("unchecked")
    @Override
    public PageFinder<T> pagedByCriteria(final DetachedCriteria detachedCriteria, final int pageNo, final int pageSize) {
        return (PageFinder<T>) this.getHibernateTemplate().execute(new HibernateCallback<T>() {

            @Override
            public T doInHibernate(Session session) throws HibernateException, SQLException {

                Criteria criteria = detachedCriteria.getExecutableCriteria(session);
                Integer totalRows = Integer.valueOf(criteria.setProjection(Projections.rowCount()).uniqueResult()
                        .toString());

                criteria.setProjection(null);
                if (totalRows.intValue() < 1) {
                    PageFinder<T> finder = new PageFinder<T>(pageNo, pageSize, totalRows);
                    finder.setData(new ArrayList<T>());
                    return (T) finder;
                } else {
                    PageFinder<T> finder = new PageFinder<T>(pageNo, pageSize, totalRows);
                    List<T> list = criteria.setFirstResult(finder.getStartOfPage()).setMaxResults(finder.getPageSize())
                            .setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY).list();
                    finder.setData(list);
                    return (T) finder;
                }
            }

        });
    }

    @Override
    public DetachedCriteria createCriteria(Criterion... criterions) {
        DetachedCriteria criteria = DetachedCriteria.forClass(this.entityClass);
        for (Criterion c : criterions) {
            criteria.add(c);
        }
        return criteria;
    }

    @Override
    public DetachedCriteria createCriteria(String orderBy, boolean isAsc, Criterion... criterions) {
        DetachedCriteria criteria = this.createCriteria(criterions);
        if (isAsc) {
            criteria.addOrder(Order.asc(orderBy));
        } else {
            criteria.addOrder(Order.desc(orderBy));
        }
        return criteria;
    }

    @Override
    public List<T> findByProperty(String propertyName, Object value) {
        return this.findByCriteria(this.createCriteria(Restrictions.eq(propertyName, value)).setResultTransformer(
                Criteria.DISTINCT_ROOT_ENTITY));
    }

    @Override
    public List<T> getAllByOrder(String orderBy, boolean isAsc) {
        Assert.hasText(orderBy);
        Order order = isAsc ? Order.asc(orderBy) : Order.desc(orderBy);
        DetachedCriteria criteria = this.createCriteria();
        criteria.setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY).addOrder(order);
        return this.findByCriteria(criteria);
    }

    @Override
    public Query createQuery(String hql, Object... values) {
        Assert.hasText(hql, "hql can't be empty");
        Query query = super.getSession().createQuery(hql);
        if (values == null) {
            return query;
        }
        for (int i = 0; i < values.length; i++) {
            query.setParameter(i, values[i]);
        }
        return query;
    }

    @Override
    public Query createQuery(String hql, Map<String, ?> values) {
        Assert.hasText(hql, "hql can't be empty");
        Query query = this.createQuery(hql);
        if (values != null) {
            query.setProperties(values);
        }
        return query;
    }

    @Override
    public Query createSQLQuery(String sql, Object... values) {
        Assert.hasText(sql, "sql can't be empty");
        Query query = super.getSession().createSQLQuery(sql);
        if (values != null) {
            for (int i = 0; i < values.length; i++) {
                query.setParameter(i, values[i]);
            }
        }
        return query;
    }

    @Override
    public Query createSQLQuery(String sql, Map<String, ?> values) {
        Assert.hasText(sql, "sql can't be empty");
        Query query = this.createSQLQuery(sql);
        if (values != null) {
            query.setProperties(values);
        }
        return query;
    }

    @SuppressWarnings("unchecked")
    @Override
    public List<T> findByHql(String hql, Object... values) {
        return this.getHibernateTemplate().find(hql, values);
    }

    @SuppressWarnings("unchecked")
    @Override
    public List<T> findByNameQuery(String queryName, Object... values) {
        Assert.hasText(queryName);
        HibernateTemplate template = this.getHibernateTemplate();
        template.setCacheQueries(true);
        return template.findByNamedQuery(queryName, values);
    }

    @Override
    public int getCountByCriteria(final DetachedCriteria detachedCriteria) {

        return ((Long) this.getHibernateTemplate().execute(new HibernateCallback<T>() {
            @SuppressWarnings("unchecked")
            @Override
            public T doInHibernate(Session session) throws HibernateException, SQLException {
                return (T) detachedCriteria.getExecutableCriteria(session).setProjection(Projections.rowCount())
                        .uniqueResult();
            }
        })).intValue();
    }
    
    public T getSumByCriteria(final DetachedCriteria detachedCriteria, final String propertyName) {
    	return this.getHibernateTemplate().execute(new HibernateCallback<T>() {
    		@SuppressWarnings("unchecked")
            @Override
    		public T doInHibernate(Session session) throws HibernateException, SQLException {
    			return (T)detachedCriteria.getExecutableCriteria(session).setProjection(Projections.sum(propertyName)).uniqueResult();
    		}
    	});
    }

    @Override
    @SuppressWarnings("unchecked")
    public List<T> findByCriteria(final DetachedCriteria detachedCriteria) {
        return (List<T>) this.getHibernateTemplate().execute(new HibernateCallback<T>() {

            @Override
            public T doInHibernate(Session session) throws HibernateException, SQLException {

                return (T) detachedCriteria.getExecutableCriteria(session).list();
            }

        });
    }
    
    @SuppressWarnings("unchecked")
    public T getObjectByHql(String hql, Object... values) {
        Query query = this.createQuery(hql, values);
        List<T> list = query.list();
        if (null != list && list.size() > 0) {
            T first = list.get(0);
            for (int i = 1; i < list.size(); i++) {
                if (list.get(i) != first) {
                    throw new NonUniqueResultException(list.size());
                }
            }
            return first;
        }
        return null;
    }

    /**
     * 去除hql的select 子句，未考虑union的情况用于pagedQuery.
     * 
     * @param hql
     * 
     * @return
     */
    private String removeSelect(String hql) {
        Assert.hasText(hql);
        int beginPos = hql.toLowerCase().indexOf("from");
        Assert.isTrue(beginPos != -1, " hql : " + hql + " must has a keyword 'from'");
        return hql.substring(beginPos);
    }

    /**
     * 去除hql的orderby 子句，用于pagedQuery.
     * 
     * @param hql
     * @return
     */
    private String removeOrders(String hql) {
        Assert.hasText(hql);
        Pattern p = Pattern.compile("order\\s*by[\\w|\\W|\\s|\\S]*", Pattern.CASE_INSENSITIVE);
        Matcher m = p.matcher(hql);
        StringBuffer sb = new StringBuffer();
        while (m.find()) {
            m.appendReplacement(sb, "");
        }
        m.appendTail(sb);
        return sb.toString();
    }

    @SuppressWarnings("unchecked")
    @Override
    public T get(String propertyName, Object value) {
        Assert.hasText(propertyName, "propertyName must not be empty");
        Assert.notNull(value, "value is required");
        String hql = "from " + this.entityClass.getName() + " as model where model." + propertyName + " = ?";
        return (T) this.getSession().createQuery(hql).setParameter(0, value).uniqueResult();
    }

}
